Sistema de Gerenciamento de Estoque

Iago Flávio
Hertz Rafael
Cauã Wendel

Introdução

  • Tema do projeto: Sistema de Gerenciamento de Estoque
  • Implementado em Python
  • Utiliza funções, classes, listas encadeadas e recursão

Objetivos do Projeto

Objetivos do Projeto

  • Criar um sistema eficiente para gerenciar estoque
  • Implementar operações básicas: adicionar, remover, atualizar e listar produtos
  • Utilizar estruturas de dados avançadas (listas encadeadas) para armazenamento

Estruturas Utilizadas

  • Funções: Para modularizar o código e melhorar a legibilidade
  • Classes: Para representar produtos e o estoque
  • Listas Encadeadas: Para armazenar os produtos de forma dinâmica
  • Recursão: Para otimizar o código das funções

Organização do projeto

  • O projeto foi organizado nos seguintes arquivos:

    • linkedlist.py: Guarda o funcionamento da lista encadeada
    • produto.py: Guarda a classe Product, responsável pelas informações dos produtos
    • estoque.py: Guarda a principal funcionalidade do sistema, a classe Stock
    • main.py: É o coração do sistema de gerenciamento de estoque, que é mostrado ao usuário
  • Criamos um guia, contendo as principais missões de cada membro da equipe

1. `adicionar_produto()` (cauã)
2. `remover_produto()` (cauã)
3. `atualizar_produto()` (hertz)
4. `listar_produtos()` (iago)
5. `buscar_produto()` (iago)
6. `buscar_por_categoria(categoria)` (hertz) --> Busca mais complexa e específica.
7. `ordenar_por_quantidade()` (iago) --> Ordena a lista de produtos por quantidade (do menor para o maior).
8. `get_action(string)` (cauã)
9. `main()`(cauã)
  • Utilizamos o GitHub para versionamento de código
# Controle de Versão com GitHub

## Configuração Inicial

Clone o repositório localmente:
   
   git clone (link_do_repositório) --> O link ainda será enviado no grupo do whatsapp do projeto pois o repositório ainda não foi criado.
   

## Fluxo de Trabalho

1. Criar uma branch para cada nova feature:

   git checkout -b feature/adicionar-produto


2. Fazer commits frequentes com mensagens descritivas:

   git commit -m "Implementa função de adicionar produto"


3. Fazer um push para o GitHub:

   git push origin feature/adicionar-produto


4. Abrir um Pull Request para revisão do código

## Documentação de Erros

1. Usar as Issues do GitHub para rastrear erros
2. Ao encontrar um bug:
   - Criar uma nova Issue descrevendo o problema
   - Adicionar labels relevantes (ex: "bug", "high-priority")
   - Atribuir a um membro da equipe, ou você mesmo caso seja o responsável

3. Ao resolver um bug:
   - Referenciar o número da Issue no commit:

     git commit -m "Corrige erro na atualização de quantidade (#42)"

   - Fechar a Issue através do Pull Request ou manualmente
  • Além disso, foi utilizado também a linguagem Quarto (.qmd) junto de seu plugin no VS Code para produção dos slides e do guia.

Principais classes utilizadas:

  • Node
  • LinkedList
  • Product
  • Stock

Classe Node

class Node:

    def __init__(self, element):
        self.element = element
        self.next = None
  • Representa um nó em uma lista encadeada
  • Cada nó contém um dado self.element e uma referência self.next para o próximo nó na lista

Classe LinkedList

class LinkedList:

    def __init__(self):
        self.head: Node = None
        self.last: Node = None
    
    #
    # Add an element to final index of list.
    #
    def add(self, element):
        node = Node(element)

        #
        # Check if the list is empty.
        #
        if self.is_empty():
            self.head = node
            self.last = node
            return
        
        self.last.next = node
        self.last = node

    def remove(self, element):
        if self.is_empty():  # Check if the list is empty.
            return None

        current = self.head
        previous = None     # Saving node previous to the node to be removed.

        while current is not None:
            if current.element == element:
                if previous is None: 
                    self.head = current.next
                    if self.head is None:  
                        self.last = None
                else:  
                    previous.next = current.next   # Updating the next so that the previous node to be removed is equal to the next of the node that will be removed.
                    if current.next is None:  
                        self.last = previous

                return element

            previous = current
            current = current.next

        return None

    #
    # Check if the linked list is empty.
    #
    def is_empty(self) -> bool:
        return self.head is None
    
    #
    # Return an array with all elements.
    #
    def get_all(self):
        elements = []

        if self.is_empty():
            return elements

        node = self.head
        while True:
            elements.append(node.element)
            node = node.next

            if node is None:
                break
        
        return elements

    #
    # Get the first occurrence of element on list.
    #
    def get(self, element):

        if self.is_empty():
            return None
        
        node = self.head
        while True:

            if node.element == element:
                return node.element
            
            node = node.next
            if node is None:
                break
        
        return None

Classe LinkedList

Seus principais métodos são:

  • add(self, element): Adiciona um novo nó
  • remove(self, element): Remove um nó
  • is_empty(self): Verifica se a lista encadeada está vazia
  • get_all(self): Retorna todos os nós da lista
  • get(self, element): Busca um nó existente na lista

Classe Product

class Product:

    def __init__(self, id, name, category, quantity, price):
        self.id = id
        self.name = name
        self.category = category
        self.quantity = quantity
        self.price = price

Classe Product

class Product:

    def __init__(self, id, name, category, quantity, price):
        self.id = id
        self.name = name
        self.category = category
        self.quantity = quantity
        self.price = price

    def __str__(self) -> str:
        return f'Product:[id={self.id}, name={self.name}, category={self.category}, quantity={self.quantity}, price={self.price}]'

Método mágico __str__: Quando a classe product for chamada em uma função print, ela retornará todas as informações do produto.

Classe Stock

É a maior classe, contendo as funções principais do projeto

from linkedlist import LinkedList
from produto import Product

class Stock:

    def __init__(self):
        self.stock = LinkedList()
        self.current_id = 0
        self.update_actions = [
            { 
                "action": "1",
                "description": "Alterar o nome do produto.", 
                "function": lambda product, new_value: setattr(product, 'name', new_value)
            },
            { 
                "action": "2", 
                "description": "Alterar a categoria do produto.", 
                "function": lambda product, new_value: setattr(product, 'category', new_value)
            },
            { 
                "action": "3", 
                "description": "Alterar a quantidade do produto.", 
                "function": lambda product, new_value: setattr(product, 'quantity', int(new_value))
            },
            { 
                "action": "4", 
                "description": "Alterar o preço do produto.", 
                "function": lambda product, new_value: setattr(product, 'price', float(new_value))
            }
        ]

    # Gerando ID novo a cada criação
    def generate_id(self):
        self.current_id += 1

        return self.current_id

    #
    # Get product if exists, else return None
    #
    def get_product(self, id):

        ## Get all existent products in stock
        all_products = self.stock.get_all()

        ## Find existent product in stock by ID
        for product in all_products:
            if id == product.id:
                return product
            
        return None

    def add_product(self, name, category, quantity, price):
        id = self.generate_id()
        new_product = Product(id, name, category, quantity, price)

        self.stock.add(new_product)
        print(f"Produto {name} adicionado com sucesso.")

    def remove_product(self, id):
        product = self.get_product(id)

        if product is None:
            print("Produto não encontrado.")
            return
        
        self.stock.remove(product)
        print(f"Produto '{product.name}' removido com sucesso.")

    def update_product(self, id):
        product = self.get_product(id)
        if product is None:
            return
        
        for action in self.update_actions:
            print(f"{action['action']}: {action['description']}")
        
        action_command = input("Insira o número de qual campo acima você quer alterar: ")
        
        action_object = None
        for action in self.update_actions:
            if action['action'] == action_command:
                action_object = action
        
        if action_object is None:
            print(f"Você inseriu uma ação inválida.")
            self.update_product(id)
            return

        new_value = input("Insira o novo valor para o campo informado: ")
        action_object['function'](product, new_value)


    # Get product if exists by name, else return none
    def get_product_by_name(self, name):
        all_products = self.stock.get_all()
        transformed_name = name.strip().lower()

        for product in all_products:
            if transformed_name == product.name:
                return product
        
        return None

    def get_products_by_category(self, category):
        products = []

        all_products = self.stock.get_all()
        transformed_category = category.strip().lower()

        for product in all_products:
            if transformed_category == product.category:
                products.append(product)
        
        return products

    #
    # Get all products from stock
    #
    def get_all_products(self): 
        return self.stock.get_all()

    #
    # Order by quantity from highest to lowest if stock is not empty
    #
    def order_by_quantity(self):

        if self.stock.is_empty():
            return None

        ## Get all existent produts in stock
        all_products = self.stock.get_all()

        ## Initialize ordered list
        ordered_list = []

        ## Search in all of the products list and return
        for i in range(len(all_products)):
            highest = None

            for product in all_products:
                if highest == None or product.quantity >= highest.quantity:
                    highest = product
                
            ordered_list.append(highest)
            all_products.remove(highest)

        return ordered_list

Demonstração do Código

Desafios Enfrentados e Soluções Adotadas

Implementação correta da lista encadeada através das classes Stock e LinkedList, onde a classe Stock utiliza dos métodos da LinkedList para funcionar, além da classe Product

from linkedlist import LinkedList
from produto import Product

class Stock:

Desafios Enfrentados e Soluções Adotadas

Trabalho colaborativo usando github através de pull requests

Desafios Enfrentados e Soluções Adotadas

Tratamento de erros e exceções com a função get_product_by_name() e get_product_by_category() na classe Stock

    # Get product if exists by name
    def get_product_by_name(self, name):
        all_products = self.stock.get_all()
        transformed_name = name.strip().lower()

        for product in all_products:
            if transformed_name == product.name:
                return product
        
        return None
    
    # Get list of products if exists by category
    def get_products_by_category(self, category):
        products = []

        all_products = self.stock.get_all()
        transformed_category = category.strip().lower()

        for product in all_products:
            if transformed_category == product.category:
                products.append(product)
        
        return products

Desafios Enfrentados e Soluções Adotadas

Tratamento de erros e exceções com a função get_product_by_name() e get_product_by_category() na classe Stock

Exemplo: o usuário pode inserir o nome “CaChoRRO”. Se tiver apenas um item escrito “cachorro”, ele não vai encontrar, pois precisa digitar exatamente como está, com letras minúsculas e sem espaços.

Porém, ao adicionarmos o .strip() (remove espaços) e o .lower() (deixa todas as letras minúsculas), que são métodos de string, resolvemos esse problema, e o usuário pode encontrar o que deseja.

    # Get product if exists by name
    def get_product_by_name(self, name):
        all_products = self.stock.get_all()
        transformed_name = name.strip().lower()

        for product in all_products:
            if transformed_name == product.name:
                return product
        
        return None
    
    # Get list of products if exists by category
    def get_products_by_category(self, category):
        products = []

        all_products = self.stock.get_all()
        transformed_category = category.strip().lower()

        for product in all_products:
            if transformed_category == product.category:
                products.append(product)
        
        return products

Desafios Enfrentados e Soluções Adotadas

Alocação dinâmica do ID dos produtos registrados:

Através da função generate_id(), conseguimos gerar dinamicamente o ID dos novos produtos, evitando que seja necessário inserir o ID na hora de cadastrar um novo produto no estoque

class Stock:

    # Gerando ID novo a cada criação
    def generate_id(self):
        self.current_id += 1

        return self.current_id

    # Adicionar um produto
    def add_product(self, name, category, quantity, price):
        id = self.generate_id()
        new_product = Product(id, name, category, quantity, price)

        self.stock.add(new_product)
        print(f"Produto {name} adicionado com sucesso.")

Desafios Enfrentados e Soluções Adotadas

Escolha de trabalhar com atualização por produtos em dicionários ao invés de listas devido a possibilidade do uso de BIG DATA (grandes quantidades de dados)

Nesse caso, registramos a ação self.update_actions dentro do init

def main():
    stock = Stock()

    while True:
        action = input(
            "Digite 1 - para adicionar, remover ou atualizar produtos,\n"
            "Digite 2 - para buscar produto, por categoria, por quantidade,\n"
            "Digite 3 - para listar os produtos,\n"
            "Digite Q - para sair."
            ).upper()

        print("-" * 60)

        if action == "1":
            while True:
                subaction = input(
                    "Digite 1 - para adicionar um produto,\n"
                    "Digite 2 - para remover um produto,\n"
                    "Digite 3 - para atualizar um produto,\n"
                    "Digite Q - para sair."
                    ).upper()
                
                print("-" * 60)
            
                if subaction == "1":    
                    name = input("Digite o nome do produto: ")
                    category = input("Digite a categoria do produto: ")
                    quantity = int(input("Digite a quantidade do produto: "))
                    price = float(input("Digite o preço do produto: "))
                    stock.add_product(name, category, quantity, price)
                    print("-" * 60)

                elif subaction == "2":
                    id = int(input("Digite o ID do produto a ser removido: "))
                    stock.remove_product(id)
                    print("-" * 60)

                elif subaction == "3":
                    product_id = int(input("Digite o id do produto a ser atualizado: "))
                    stock.update_product(product_id)

                elif subaction == "Q":
                    break

                else:
                    print("Ação inválida.")
class Stock:

    def __init__(self):
        self.stock = LinkedList()
        self.current_id = 0
        self.update_actions = [
            { 
                "action": "1",
                "description": "Alterar o nome do produto.", 
                "function": lambda product, new_value: setattr(product, 'name', new_value)
            },
            { 
                "action": "2", 
                "description": "Alterar a categoria do produto.", 
                "function": lambda product, new_value: setattr(product, 'category', new_value)
            },
            { 
                "action": "3", 
                "description": "Alterar a quantidade do produto.", 
                "function": lambda product, new_value: setattr(product, 'quantity', int(new_value))
            },
            { 
                "action": "4", 
                "description": "Alterar o preço do produto.", 
                "function": lambda product, new_value: setattr(product, 'price', float(new_value))
            }
        ]

Conclusão

  • Conseguimos criar um sistema de gerenciamento de estoque com funções básicas
  • Aprendemos a trabalhar de maneira colaborativa para a construção do projeto
  • Melhorias futuras:
    • É possível incrementar funções que retornem uma lista de produtos ao invés de um produto apenas, como na função get_product() da classe Stock
    • Além disso, é possível realizar a criação de um front-end mais robusto, como a hospedagem em um site, junto de um sistema de usuários
    • Podemos também realizar a implementação de um banco de dados para os produtos do estoque, usando um arquivo básico como uma planilha do excel ou um arquivo .csv, podendo no futuro mudar para a linguagem SQL

Perguntas ?

Obrigado pela atenção!